﻿using System;
using SoftwareConsulting.BI11.NumericalMethods.Interfaces;
using SoftwareConsulting.BI11.NumericalMethods.DhbFunctionEvaluation;
using SoftwareConsulting.BI11.NumericalMethods.Iterations;
using SoftwareConsulting.BI11.Studio.InternalServices;

namespace SoftwareConsulting.BI11.NumericalMethods.Statistics
{    
    /// <summary>
    /// Класс распределения Фишера-Снидекора (для F-теста)
    /// </summary>
    public sealed class FisherSnedecorDistribution: ProbabilityDensityFunction
    {	    
        private int _firstFreedomDegree;//первая степень свободы	    
        private int _secondFreedomDegree;//вторая степень свободы	    
        private double _norm;//норма (хранится для ускорения)	    
        private IncompleteBetaFunction _incompleteBetaFunction;	    
        private ChiSquareDistribution _chiSquareDistribution1;//вспомогательное распределение для генерации случайных чисел
        private ChiSquareDistribution _chiSquareDistribution2;//вспомогательное распределение для генерации случайных чисел

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="firstFreedomDegree">первая степень свободы</param>
        /// <param name="secondFreedomDegree">вторая степень свободы</param>
        public FisherSnedecorDistribution(int firstFreedomDegree, int secondFreedomDegree)
        {
            if (firstFreedomDegree <= 0)
                throw new ArgumentOutOfRangeException("firstFreedomDegree");
            if (secondFreedomDegree <= 0)
                throw new ArgumentOutOfRangeException("secondFreedomDegree");
            SetParameters(firstFreedomDegree, secondFreedomDegree);
        }

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="histogram">Гистограмма, из которой вытягиваются параметры по наилучшим предположениям</param>
        public FisherSnedecorDistribution(IHistogram histogram)
        {
            if (histogram.Minimum < 0)
                throw new ArgumentOutOfRangeException();
            int n2 = (int)Math.Round(2 / (1 - 1 / histogram.Average));
            if (n2 <= 0)
                throw new ArgumentOutOfRangeException();           
            double a = 1 - (n2 - 2) * (n2 - 4) * histogram.Variance / (2 * 2 * n2);
            int n1 = (int)Math.Round(0.7 * (n2 - 2) / a);
            if (n1 <= 0)
                throw new ArgumentOutOfRangeException();
            SetParameters(n1, n2);
        }

        /// <summary>
        /// Возвращает среднее распределения
        /// </summary>
        public override double Average
        {
            get 
            { 
                return _secondFreedomDegree > 2 ? _secondFreedomDegree / (_secondFreedomDegree - 2) : double.NaN; 
            }
            set 
            {
                Assert.Fail(Assert.Id.Assert00280);//для этого распределения недоступно
            }
        }

        /// <summary>
        /// Возвращает уровень доверия
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        public double GetConfidenceLevel(double x)
        {            
            if (x <= 0)
                throw new ArgumentOutOfRangeException("x");
            return x < 1 ? (1 - GetDistributionValue(x) + GetDistributionValue(1 / x)) * 100
                         : (1 - GetDistributionValue(1 / x) + GetDistributionValue(x)) * 100;
        }
        
        /// <summary>
        /// Задает новые параметры (степени свободы) и пересчитывает норму
        /// </summary>        
        /// <param name="firstFreedomDegree">первая степень свободы</param>
        /// <param name="secondFreedomDegree">вторая степень свободы</param>
        public void SetParameters(int firstFreedomDegree, int secondFreedomDegree)
        {
            _firstFreedomDegree = firstFreedomDegree;
            _secondFreedomDegree = secondFreedomDegree;
            double nn1 = 0.5 * firstFreedomDegree;
            double nn2 = 0.5 * secondFreedomDegree;
            _norm = nn1 * Math.Log(firstFreedomDegree) + nn2 * Math.Log(secondFreedomDegree) - GammaFunction.LogBeta(nn1, nn2);
            _incompleteBetaFunction = null;
            _chiSquareDistribution1 = null;
            _chiSquareDistribution2 = null;
        }

        /// <summary>
        /// Возвращает значение функции плотности распределения
        /// </summary>
        /// <param name="x"></param>
        /// <returns>вероятность, нахождения случайной переменной меньше или равной, чем X</returns>
        public override double GetDistributionValue(double x)
        {
            return IncompleteBetaFunction.GetValue(_secondFreedomDegree / (x * _firstFreedomDegree + _secondFreedomDegree));
        }

        /// <summary>
        /// Возвращает незавершенную бета-функцию
        /// </summary>
        private IncompleteBetaFunction IncompleteBetaFunction
        {
            get
            {
                if (_incompleteBetaFunction == null)
                    _incompleteBetaFunction = new IncompleteBetaFunction(
                                                        0.5 * _firstFreedomDegree, 0.5 * _secondFreedomDegree);
                return _incompleteBetaFunction;
            }
        }

        /// <summary>
        /// Возвращает название распределения
        /// </summary>
        public override string Name
        {
            get 
            { 
                return "Fisher-Snedecor distribution"; 
            }
        }

        /// <summary>
        /// Возвращает/устанавливает параметры распределения
        /// </summary>
        public override double[] Parameters
        {
            get 
            { 
                return new double[] { _firstFreedomDegree, _secondFreedomDegree }; 
            }
            set
            {
                SetParameters((int)Math.Round(value[0]), (int)Math.Round(value[1]));
            }
        }

        /// <summary>
        /// Возвращает случайное вещественное число в соответствие с распределением
        /// </summary>
        /// <returns></returns>
        public override double GetRandomValue()
        {            
            if (_chiSquareDistribution1 == null)
            {
                _chiSquareDistribution1 = new ChiSquareDistribution(_firstFreedomDegree);
                _chiSquareDistribution2 = new ChiSquareDistribution(_secondFreedomDegree);
            }
            return _chiSquareDistribution1.GetRandomValue() * _secondFreedomDegree
                    / (_chiSquareDistribution2.GetRandomValue() * _firstFreedomDegree);
        }

        /// <summary>
        /// Возвращает как строку
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return string.Format("{0} ({1},{2})", Name, _firstFreedomDegree, _secondFreedomDegree);
        }

        /// <summary>
        /// Вычисляет значение, основываясь на функции плотности
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        public override double GetValue(double x)
        {
            return x > 0
                    ? Math.Exp(_norm + Math.Log(x) * (_firstFreedomDegree / 2 - 1)
                                    - Math.Log(x * _firstFreedomDegree + _secondFreedomDegree)
                                                    * ((_firstFreedomDegree + _secondFreedomDegree) / 2))
                    : 0;
        }

        /// <summary>
        /// Возвращает дисперсию распределения
        /// </summary>
        public override double Variance
        {
            get
            {
                return _secondFreedomDegree > 4
                        ? _secondFreedomDegree * _secondFreedomDegree * 2 * (_firstFreedomDegree + _secondFreedomDegree + 2)
                                            / (_firstFreedomDegree * (_secondFreedomDegree - 2) * (_secondFreedomDegree - 4))
                        : double.NaN;
            }
        }
    }
}
